/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "../core/core_main.h"
#include "win_local.h"


sysWin_t			sys;


/*
 ==============================================================================

 DEDICATED CONSOLE

 ==============================================================================
*/

#define CONSOLE_WINDOW_NAME			ENGINE_NAME " Console"
#define CONSOLE_WINDOW_CLASS		ENGINE_NAME " ConsoleWnd"
#define CONSOLE_WINDOW_STYLE		(WS_OVERLAPPED | WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX)

#define MAX_CONSOLE_OUTPUT			32768
#define MAX_CONSOLE_INPUT			256

#define MAX_CONSOLE_HISTORY			32

#define MAX_CONSOLE_CMDS			4096

typedef struct {
	int				outputLength;					// To keep track of output buffer length

	bool			onError;						// If true, we're on a fatal error
	bool			flashError;						// If true, flash error message to red

	// History buffer
	char			historyText[MAX_CONSOLE_HISTORY][MAX_CONSOLE_INPUT];
	int				historyCount;
	int				historyLine;

	// Command auto-completion
	const char *	cmdList[MAX_CONSOLE_CMDS];
	char			cmdArgs[MAX_CONSOLE_INPUT];
	int				cmdCount;
	int				cmdIndex;

	// Window stuff
	HWND			hWnd;
	HWND			hWndError;
	HWND			hWndOutput;
	HWND			hWndInput;
	HWND			hWndCopy;
	HWND			hWndClear;
	HWND			hWndQuit;

	HFONT			hFont1;
	HFONT			hFont2;

	HBRUSH			hBrush1;
	HBRUSH			hBrush2;

	WNDPROC			defInputProc;
} sysConsole_t;

static sysConsole_t	sys_console;


/*
 ==================
 Sys_SortCommandList
 ==================
*/
static int Sys_SortCommandList (const void *elem1, const void *elem2){

	const char	*cmd1 = *(const char **)elem1;
	const char	*cmd2 = *(const char **)elem2;

	return Str_Compare(cmd1, cmd2);
}

/*
 ==================
 Sys_CompleteCommandCallback
 ==================
*/
static void Sys_CompleteCommandCallback (const char *found){

	if (sys_console.cmdCount == MAX_CONSOLE_CMDS)
		return;

	sys_console.cmdList[sys_console.cmdCount++] = Mem_CopyString(found, TAG_COMMON);
}

/*
 ==================
 Sys_CompleteCommand
 ==================
*/
static void Sys_CompleteCommand (void){

	cvar_t		*cvar;
	const char	*cmd1, *cmd2;
	char		text[MAX_CONSOLE_INPUT], partial[MAX_CONSOLE_INPUT];
	int			i, len;

	GetWindowText(sys_console.hWndInput, text, sizeof(text));

	// Free the auto-complete list
	for (i = 0; i < sys_console.cmdCount; i++)
		Mem_Free(sys_console.cmdList[i]);

	sys_console.cmdArgs[0] = 0;
	sys_console.cmdCount = 0;
	sys_console.cmdIndex = -1;

	// Tokenize the string
	if (!text[0])
		return;		// Nothing to search for

	Cmd_TokenizeString(text);

	// Find matching commands and variables
	Cmd_CompleteCommand(Cmd_Argv(0), Sys_CompleteCommandCallback);
	CVar_CompleteVariable(Cmd_Argv(0), Sys_CompleteCommandCallback);

	if (!sys_console.cmdCount)
		return;		// Nothing was found

	// Copy the arguments, if available
	Str_Copy(sys_console.cmdArgs, Cmd_Args(1, -1, false), sizeof(sys_console.cmdArgs));

	if (sys_console.cmdCount == 1){
		// Only one was found, so copy it to the input edit box
		if (sys_console.cmdArgs[0])
			Str_SPrintf(text, sizeof(text), "%s %s", sys_console.cmdList[0], sys_console.cmdArgs);
		else
			Str_SPrintf(text, sizeof(text), "%s ", sys_console.cmdList[0]);

		SetWindowText(sys_console.hWndInput, text);

		SendMessage(sys_console.hWndInput, EM_SETSEL, 0, -1);
		SendMessage(sys_console.hWndInput, EM_SETSEL, -1, -1);
	}
	else {
		// Sort the commands
		qsort(sys_console.cmdList, sys_console.cmdCount, sizeof(char *), Sys_SortCommandList);

		// Print the commands
		Com_Printf("]%s\n", Cmd_Argv(0));

		for (i = 0; i < sys_console.cmdCount; i++){
			Com_Printf("    %s", sys_console.cmdList[i]);

			cvar = CVar_FindVariable(sys_console.cmdList[i]);
			if (cvar)
				Com_Printf(" = \"%s" S_COLOR_DEFAULT "\"\n", cvar->value);
			else
				Com_Printf("\n");
		}

		// Find the number of matching characters between the first and the
		// last command in the list and copy it
		cmd1 = sys_console.cmdList[0];
		cmd2 = sys_console.cmdList[sys_console.cmdCount-1];

		len = 0;
		while (*cmd1 && *cmd2 && *cmd1 == *cmd2){
			cmd1++;
			cmd2++;

			partial[len] = sys_console.cmdList[0][len];
			len++;
		}
		partial[len] = 0;

		// Copy the match to the input edit box
		if (sys_console.cmdArgs[0])
			Str_SPrintf(text, sizeof(text), "%s %s", partial, sys_console.cmdArgs);
		else
			Str_SPrintf(text, sizeof(text), "%s", partial);

		SetWindowText(sys_console.hWndInput, text);

		SendMessage(sys_console.hWndInput, EM_SETSEL, 0, len);
		SendMessage(sys_console.hWndInput, EM_SETSEL, -1, -1);
	}
}

/*
 ==================
 Sys_ConsoleWindowProc
 ==================
*/
static LRESULT CALLBACK Sys_ConsoleWindowProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){

	switch (uMsg){
	case WM_CREATE:
		sys.hWndConsole = hWnd;

		break;
	case WM_DESTROY:
		sys.hWndConsole = NULL;

		break;
	case WM_ACTIVATE:
		if (LOWORD(wParam) != WA_INACTIVE){
			SetFocus(sys_console.hWndInput);
			return 0;
		}

		break;
	case WM_CLOSE:
		PostQuitMessage(0);

		break;
	case WM_COMMAND:
		if (HIWORD(wParam) == BN_CLICKED){
			if ((HWND)lParam == sys_console.hWndCopy){
				SendMessage(sys_console.hWndOutput, EM_SETSEL, 0, -1);
				SendMessage(sys_console.hWndOutput, WM_COPY, 0, 0);

				break;
			}

			if ((HWND)lParam == sys_console.hWndClear){
				SendMessage(sys_console.hWndOutput, EM_SETSEL, 0, -1);
				SendMessage(sys_console.hWndOutput, EM_REPLACESEL, FALSE, (LPARAM)"");

				break;
			}

			if ((HWND)lParam == sys_console.hWndQuit)
				PostQuitMessage(0);
		}

		break;
	case WM_CTLCOLORSTATIC:
		if ((HWND)lParam == sys_console.hWndError){
			if (sys_console.flashError){
				SetBkColor((HDC)wParam, RGB(192, 192, 192));
				SetTextColor((HDC)wParam, RGB(255, 0, 0));
			}
			else {
				SetBkColor((HDC)wParam, RGB(192, 192, 192));
				SetTextColor((HDC)wParam, RGB(0, 0, 0));
			}

			return (LRESULT)sys_console.hBrush1;
		}

		if ((HWND)lParam == sys_console.hWndOutput){
			SetBkColor((HDC)wParam, RGB(25, 25, 25));
			SetTextColor((HDC)wParam, RGB(0, 128, 255));

			return (LRESULT)sys_console.hBrush2;
		}

		break;
	case WM_TIMER:
		if (sys_console.onError){
			sys_console.flashError = !sys_console.flashError;

			InvalidateRect(sys_console.hWndError, NULL, FALSE);

			if (GetForegroundWindow() != sys_console.hWnd)
				FlashWindow(sys_console.hWnd, TRUE);
		}

		break;
	}

	// Pass all unhandled messages to DefWindowProc
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

/*
 ==================
 Sys_ConsoleInputProc
 ==================
*/
static LRESULT CALLBACK Sys_ConsoleInputProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){

	char	text[MAX_CONSOLE_INPUT];
	int		i;

	switch (uMsg){
	case WM_KEYDOWN:
		// Auto-complete command
		if (wParam == VK_TAB){
			if (sys_console.cmdCount <= 1)
				Sys_CompleteCommand();
			else {
				// Cycle through the commands
				if (++sys_console.cmdIndex == sys_console.cmdCount)
					sys_console.cmdIndex = 0;

				if (sys_console.cmdArgs[0])
					Str_SPrintf(text, sizeof(text), "%s %s", sys_console.cmdList[sys_console.cmdIndex], sys_console.cmdArgs);
				else
					Str_SPrintf(text, sizeof(text), "%s ", sys_console.cmdList[sys_console.cmdIndex]);

				SetWindowText(sys_console.hWndInput, text);

				SendMessage(sys_console.hWndInput, EM_SETSEL, 0, -1);
				SendMessage(sys_console.hWndInput, EM_SETSEL, -1, -1);
			}

			return 0;
		}

		// Any other typing frees the auto-complete list
		for (i = 0; i < sys_console.cmdCount; i++)
			Mem_Free(sys_console.cmdList[i]);

		sys_console.cmdArgs[0] = 0;
		sys_console.cmdCount = 0;
		sys_console.cmdIndex = -1;

		// Execute a command
		if (wParam == VK_RETURN){
			GetWindowText(sys_console.hWndInput, text, sizeof(text));

			Com_Printf("]%s\n", text);

			if (!text[0])
				return 0;

			// Add the command text
			Cmd_AppendText(text);
			Cmd_AppendText("\n");

			// Add to history and clear input
			Str_Copy(sys_console.historyText[sys_console.historyCount], text, sizeof(sys_console.historyText[sys_console.historyCount]));

			sys_console.historyCount = (sys_console.historyCount + 1) & (MAX_CONSOLE_HISTORY-1);
			sys_console.historyLine = sys_console.historyCount;

			SetWindowText(sys_console.hWndInput, "");

			return 0;
		}

		// Go to previous history line
		if (wParam == VK_UP){
			do {
				sys_console.historyLine = (sys_console.historyLine - 1) & (MAX_CONSOLE_HISTORY-1);
			} while (sys_console.historyLine != sys_console.historyCount && !sys_console.historyText[sys_console.historyLine][0]);

			if (sys_console.historyLine == sys_console.historyCount)
				sys_console.historyLine = (sys_console.historyLine - sys_console.historyCount) & (MAX_CONSOLE_HISTORY-1);

			SetWindowText(sys_console.hWndInput, sys_console.historyText[sys_console.historyLine]);

			SendMessage(sys_console.hWndInput, EM_SETSEL, 0, -1);
			SendMessage(sys_console.hWndInput, EM_SETSEL, -1, -1);

			return 0;
		}

		// Go to next history line
		if (wParam == VK_DOWN){
			if (sys_console.historyLine == sys_console.historyCount)
				return 0;

			do {
				sys_console.historyLine = (sys_console.historyLine + 1) & (MAX_CONSOLE_HISTORY-1);
			} while (sys_console.historyLine != sys_console.historyCount && !sys_console.historyText[sys_console.historyLine][0]);

			if (sys_console.historyLine == sys_console.historyCount)
				SetWindowText(sys_console.hWndInput, "");
			else {
				SetWindowText(sys_console.hWndInput, sys_console.historyText[sys_console.historyLine]);

				SendMessage(sys_console.hWndInput, EM_SETSEL, 0, -1);
				SendMessage(sys_console.hWndInput, EM_SETSEL, -1, -1);
			}

			return 0;
		}

		break;
	case WM_CHAR:
		if (wParam == VK_TAB || wParam == VK_RETURN)
			return 0;		// Keep it from beeping

		break;
	}

	// Pass all unhandled messages to DefWindowProc
	return CallWindowProc(sys_console.defInputProc, hWnd, uMsg, wParam, lParam);
}

/*
 ==================
 Sys_CreateConsole
 ==================
*/
static void Sys_CreateConsole (void){

	WNDCLASSEX	wndClass;
	RECT		rect;
	int			screenWidth, screenHeight;
	int			x, y, w, h;

	// Center the window in the desktop
	screenWidth = GetSystemMetrics(SM_CXSCREEN);
	screenHeight = GetSystemMetrics(SM_CYSCREEN);

	rect.left = (screenWidth - 540) / 2;
	rect.top = (screenHeight - 450) / 2;
	rect.right = rect.left + 540;
	rect.bottom = rect.top + 450;

	AdjustWindowRectEx(&rect, CONSOLE_WINDOW_STYLE, FALSE, 0);

	x = rect.left;
	y = rect.top;
	w = rect.right - rect.left;
	h = rect.bottom - rect.top;

	// Register the frame class
	wndClass.cbSize = sizeof(WNDCLASSEX);
	wndClass.style = 0;
	wndClass.lpfnWndProc = Sys_ConsoleWindowProc;
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = sys.hInstance;
	wndClass.hIcon = LoadIcon(sys.hInstance, MAKEINTRESOURCE(IDI_ICON1));
	wndClass.hIconSm = 0;
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
	wndClass.lpszMenuName = 0;
	wndClass.lpszClassName = CONSOLE_WINDOW_CLASS;

	if (!RegisterClassEx(&wndClass)){
		MessageBox(NULL, "Could not register console window class", "ERROR", MB_OK | MB_ICONERROR | MB_TASKMODAL);
		ExitProcess(0);
	}

	// Create the window
	sys_console.hWnd = CreateWindowEx(0, CONSOLE_WINDOW_CLASS, CONSOLE_WINDOW_NAME, CONSOLE_WINDOW_STYLE, x, y, w, h, NULL, NULL, sys.hInstance, NULL);
	if (!sys_console.hWnd){
		UnregisterClass(CONSOLE_WINDOW_CLASS, sys.hInstance);

		MessageBox(NULL, "Could not create console window", "ERROR", MB_OK | MB_ICONERROR | MB_TASKMODAL);
		ExitProcess(0);
	}

	// Create the controls
	sys_console.hWndError = CreateWindowEx(0, "STATIC", "", WS_CHILD | SS_SUNKEN | SS_LEFT, 8, 8, 524, 32, sys_console.hWnd, NULL, sys.hInstance, NULL);
	sys_console.hWndOutput = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY, 8, 44, 524, 338, sys_console.hWnd, NULL, sys.hInstance, NULL);
	sys_console.hWndInput = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_LEFT | ES_AUTOHSCROLL, 8, 386, 524, 20, sys_console.hWnd, NULL, sys.hInstance, NULL);
	sys_console.hWndCopy = CreateWindowEx(0, "BUTTON", "Copy", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER | BS_VCENTER, 8, 418, 80, 25, sys_console.hWnd, NULL, sys.hInstance, NULL);
	sys_console.hWndClear = CreateWindowEx(0, "BUTTON", "Clear", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER | BS_VCENTER, 92, 418, 80, 25, sys_console.hWnd, NULL, sys.hInstance, NULL);
	sys_console.hWndQuit = CreateWindowEx(0, "BUTTON", "Quit", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER | BS_VCENTER, 452, 418, 80, 25, sys_console.hWnd, NULL, sys.hInstance, NULL);

	// Create and set the fonts
	sys_console.hFont1 = CreateFont(14, 0, 0, 0, FW_LIGHT, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FIXED_PITCH | FF_MODERN, "Courier New");
	sys_console.hFont2 = CreateFont(14, 0, 0, 0, FW_LIGHT, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FIXED_PITCH | FF_MODERN, "MS Sans Serif");

	SendMessage(sys_console.hWndError, WM_SETFONT, (WPARAM)sys_console.hFont1, FALSE);
	SendMessage(sys_console.hWndOutput, WM_SETFONT, (WPARAM)sys_console.hFont1, FALSE);
	SendMessage(sys_console.hWndInput, WM_SETFONT, (WPARAM)sys_console.hFont1, FALSE);
	SendMessage(sys_console.hWndCopy, WM_SETFONT, (WPARAM)sys_console.hFont2, FALSE);
	SendMessage(sys_console.hWndClear, WM_SETFONT, (WPARAM)sys_console.hFont2, FALSE);
	SendMessage(sys_console.hWndQuit, WM_SETFONT, (WPARAM)sys_console.hFont2, FALSE);

	// Create the brushes
	sys_console.hBrush1 = CreateSolidBrush(RGB(192, 192, 192));
	sys_console.hBrush2 = CreateSolidBrush(RGB(25, 25, 25));

	// Subclass input edit box
	sys_console.defInputProc = (WNDPROC)SetWindowLong(sys_console.hWndInput, GWL_WNDPROC, (LONG)Sys_ConsoleInputProc);

	// Set a timer for flashing error messages
	SetTimer(sys_console.hWnd, 1, 1000, NULL);

	// Set text limit for the edit boxes
	SendMessage(sys_console.hWndOutput, EM_SETLIMITTEXT, (WPARAM)(MAX_CONSOLE_OUTPUT-1), 0);
	SendMessage(sys_console.hWndInput, EM_SETLIMITTEXT, (WPARAM)(MAX_CONSOLE_INPUT-1), 0);

	// Show the window
	ShowWindow(sys_console.hWnd, SW_SHOW);
	UpdateWindow(sys_console.hWnd);
	SetForegroundWindow(sys_console.hWnd);
	SetFocus(sys_console.hWndInput);
}

/*
 ==================
 Sys_DestroyConsole
 ==================
*/
static void Sys_DestroyConsole (void){

	KillTimer(sys_console.hWnd, 1);

	if (sys_console.defInputProc)
		SetWindowLong(sys_console.hWndInput, GWL_WNDPROC, (LONG)sys_console.defInputProc);

	if (sys_console.hBrush1)
		DeleteObject(sys_console.hBrush1);
	if (sys_console.hBrush2)
		DeleteObject(sys_console.hBrush2);

	if (sys_console.hFont1)
		DeleteObject(sys_console.hFont1);
	if (sys_console.hFont2)
		DeleteObject(sys_console.hFont2);

	ShowWindow(sys_console.hWnd, SW_HIDE);
	DestroyWindow(sys_console.hWnd);
	UnregisterClass(CONSOLE_WINDOW_CLASS, sys.hInstance);

	memset(&sys_console, 0, sizeof(sysConsole_t));
}

/*
 ==================
 Sys_ConsolePrint
 ==================
*/
static void Sys_ConsolePrint (const char *text){

	char	buffer[MAX_PRINT_MESSAGE];
	int		length = 0;

	// Copy into an intermediate buffer
	while (*text && (length < MAX_PRINT_MESSAGE - 2)){
		// Copy \r\n or convert \n\r to \r\n
		if ((*text == '\r' && text[1] == '\n') || (*text == '\n' && text[1] == '\r')){
			buffer[length++] = '\r';
			buffer[length++] = '\n';

			text += 2;
			continue;
		}

		// Convert \n or \r to \r\n
		if (*text == '\n' || *text == '\r'){
			buffer[length++] = '\r';
			buffer[length++] = '\n';

			text += 1;
			continue;
		}

		// Ignore color escape sequences
		if (Str_IsColor(text)){
			text += 2;
			continue;
		}

		// Ignore non-printable characters
		if (*text < ' '){
			text += 1;
			continue;
		}

		// Copy the character
		buffer[length++] = *text++;
	}

	buffer[length] = 0;

	// Check for overflow
	sys_console.outputLength += length;
	if (sys_console.outputLength >= MAX_CONSOLE_OUTPUT){
		sys_console.outputLength = length;

		SendMessage(sys_console.hWndOutput, EM_SETSEL, 0, -1);
	}

	// Scroll down
	SendMessage(sys_console.hWndOutput, EM_LINESCROLL, 0, 0xFFFF);
	SendMessage(sys_console.hWndOutput, EM_SCROLLCARET, 0, 0);

	// Add the text
	SendMessage(sys_console.hWndOutput, EM_REPLACESEL, FALSE, (LPARAM)buffer);

	// Update
	UpdateWindow(sys_console.hWnd);
}

/*
 ==================
 Sys_SetConsoleError
 ==================
*/
static void Sys_SetConsoleError (const char *text){

	sys_console.onError = true;

	SetWindowText(sys_console.hWndError, text);

	ShowWindow(sys_console.hWndError, SW_SHOW);
	ShowWindow(sys_console.hWndInput, SW_HIDE);

	// Show the window
	ShowWindow(sys_console.hWnd, SW_SHOW);
	UpdateWindow(sys_console.hWnd);
	SetForegroundWindow(sys_console.hWnd);
	SetFocus(sys_console.hWnd);
}

/*
 ==================
 Sys_ShowConsole
 ==================
*/
void Sys_ShowConsole (bool show){

	if (!show){
		// Hide the window
		ShowWindow(sys_console.hWnd, SW_HIDE);
		return;
	}

	// Show the window
	ShowWindow(sys_console.hWnd, SW_SHOW);
	UpdateWindow(sys_console.hWnd);
	SetForegroundWindow(sys_console.hWnd);
	SetFocus(sys_console.hWndInput);
}


/*
 ==============================================================================

 FILE SYSTEM

 ==============================================================================
*/

#define MAX_LIST_FILES				65536


/*
 ==================
 Sys_SortFileList
 ==================
*/
static int Sys_SortFileList (const void *elem1, const void *elem2){

	const char	*name1 = *(const char **)elem1;
	const char	*name2 = *(const char **)elem2;

	return Str_Compare(name1, name2);
}

/*
 ==================
 Sys_AddFileToList
 ==================
*/
static int Sys_AddFileToList (const char *name, const char **files, int fileCount){

	if (fileCount == MAX_LIST_FILES)
		return fileCount;	// Too many files

	// Add it
	files[fileCount++] = Mem_CopyString(name, TAG_TEMP);

	return fileCount;
}

/*
 ==================
 Sys_ListFiles
 ==================
*/
const char **Sys_ListFiles (const char *directory, const char *extension, bool sort, int *numFiles){

	WIN32_FIND_DATA	findInfo;
	HANDLE			findHandle;
	BOOL			findRes = TRUE;
	bool			listDirectories, listFiles;
	char			name[MAX_OSPATH];
	const char		**fileList;
	const char		*files[MAX_LIST_FILES];
	int				fileCount = 0;
	int				i;

	if (extension != NULL && !Str_Compare(extension, "/")){
		listDirectories = true;
		listFiles = false;
	}
	else {
		listDirectories = false;
		listFiles = true;
	}

	Str_SPrintf(name, sizeof(name), "%s/*", directory);

	// Find the first file
	findHandle = FindFirstFile(name, &findInfo);
	if (findHandle == INVALID_HANDLE_VALUE){
		*numFiles = 0;
		return NULL;
	}

	while (findRes == TRUE){
		// Check for invalid file name
		if (!Str_Compare(findInfo.cFileName, ".") || !Str_Compare(findInfo.cFileName, "..")){
			findRes = FindNextFile(findHandle, &findInfo);
			continue;
		}

		// Add it
		if (findInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
			if (listDirectories)
				fileCount = Sys_AddFileToList(findInfo.cFileName, files, fileCount);
		}
		else {
			if (listFiles){
				if (extension){
					Str_ExtractFileExtension(findInfo.cFileName, name, sizeof(name));
					if (!Str_ICompare(extension, name))
						fileCount = Sys_AddFileToList(findInfo.cFileName, files, fileCount);
				}
				else
					fileCount = Sys_AddFileToList(findInfo.cFileName, files, fileCount);
			}
		}

		// Find the next file
		findRes = FindNextFile(findHandle, &findInfo);
	}

	FindClose(findHandle);

	if (!fileCount){
		*numFiles = 0;
		return NULL;
	}

	// Sort the list if desired
	if (sort)
		qsort(files, fileCount, sizeof(char *), Sys_SortFileList);

	// Copy the list
	fileList = (const char **)Mem_Alloc((fileCount + 1) * sizeof(char *), TAG_TEMP);

	for (i = 0; i < fileCount; i++)
		fileList[i] = files[i];

	fileList[i] = NULL;

	*numFiles = fileCount;

	return fileList;
}

/*
 ==================
 Sys_RecursiveListFilteredFiles
 ==================
*/
static int Sys_RecursiveListFilteredFiles (const char *directory, const char *subdirectory, const char *filter, const char **files, int fileCount){

	WIN32_FIND_DATA	findInfo;
	HANDLE			findHandle;
	BOOL			findRes = TRUE;
	char			name[MAX_OSPATH];

	if (subdirectory)
		Str_SPrintf(name, sizeof(name), "%s/%s/*", directory, subdirectory);
	else
		Str_SPrintf(name, sizeof(name), "%s/*", directory);

	// Find the first file
	findHandle = FindFirstFile(name, &findInfo);
	if (findHandle == INVALID_HANDLE_VALUE)
		return fileCount;

	while (findRes == TRUE){
		// Check for invalid file name
		if (!Str_Compare(findInfo.cFileName, ".") || !Str_Compare(findInfo.cFileName, "..")){
			findRes = FindNextFile(findHandle, &findInfo);
			continue;
		}

		// Copy the name
		if (subdirectory)
			Str_SPrintf(name, sizeof(name), "%s/%s", subdirectory, findInfo.cFileName);
		else
			Str_SPrintf(name, sizeof(name), "%s", findInfo.cFileName);

		// If a directory, recurse into it
		if (findInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			fileCount = Sys_RecursiveListFilteredFiles(directory, name, filter, files, fileCount);

		// Match filter
		if (!Str_MatchFilter(name, filter, false)){
			findRes = FindNextFile(findHandle, &findInfo);
			continue;
		}

		// Add it
		fileCount = Sys_AddFileToList(name, files, fileCount);

		// Find the next file
		findRes = FindNextFile(findHandle, &findInfo);
	}

	FindClose(findHandle);

	return fileCount;
}

/*
 ==================
 Sys_ListFilteredFiles
 ==================
*/
const char **Sys_ListFilteredFiles (const char *directory, const char *filter, bool sort, int *numFiles){

	const char	**fileList;
	const char	*files[MAX_LIST_FILES];
	int			fileCount = 0;
	int			i;

	// List files
	fileCount = Sys_RecursiveListFilteredFiles(directory, NULL, filter, files, 0);
	if (!fileCount){
		*numFiles = 0;
		return NULL;
	}

	// Sort the list if desired
	if (sort)
		qsort(files, fileCount, sizeof(char *), Sys_SortFileList);

	// Copy the list
	fileList = (const char **)Mem_Alloc((fileCount + 1) * sizeof(char *), TAG_TEMP);

	for (i = 0; i < fileCount; i++)
		fileList[i] = files[i];

	fileList[i] = NULL;

	*numFiles = fileCount;

	return fileList;
}

/*
 ==================
 Sys_FreeFileList
 ==================
*/
void Sys_FreeFileList (const char **fileList){

	int		i;

	if (!fileList)
		return;

	for (i = 0; fileList[i]; i++)
		Mem_Free(fileList[i]);

	Mem_Free(fileList);
}

/*
 ==================
 Sys_CreateDirectory
 ==================
*/
void Sys_CreateDirectory (const char *directory){

	CreateDirectory(directory, NULL);
}

/*
 ==================
 Sys_DefaultBaseDirectory
 ==================
*/
const char *Sys_DefaultBaseDirectory (void){

	static char	baseDirectory[MAX_OSPATH];

	if (!GetCurrentDirectory(sizeof(baseDirectory), baseDirectory))
		Com_Error(true, "Couldn't get current directory");

	return baseDirectory;
}

/*
 ==================
 Sys_DefaultSaveDirectory
 ==================
*/
const char *Sys_DefaultSaveDirectory (void){

	static char	saveDirectory[MAX_OSPATH];

	if (!GetCurrentDirectory(sizeof(saveDirectory), saveDirectory))
		Com_Error(true, "Couldn't get current directory");

	return saveDirectory;
}


/*
 ==============================================================================

 LIBRARY LOADING

 ==============================================================================
*/


/*
 ==================
 Sys_LoadLibrary
 ==================
*/
void *Sys_LoadLibrary (const char *name){

	char		path[MAX_OSPATH];
	byte		*buffer;
	int			size;
	const char	*savePath, *game;

	// Load the file, then save it (overwritting an existing file).
	// This ensures the library comes from a pack file if we are connected to a
	// pure server, but also allows the library to come from a directory tree
	// for development purposes.
	Str_SPrintf(path, sizeof(path), "%s%s.dll", name, CPU_STRING);

	size = FS_ReadFile(path, (void **)&buffer);
	if (!buffer)
		return NULL;

	if (!FS_WriteFile(path, buffer, size)){
		FS_FreeFile(buffer);
		return NULL;
	}

	FS_FreeFile(buffer);

	// Now load the actual library
	savePath = CVar_GetVariableString("fs_savePath");
	game = CVar_GetVariableString("fs_game");

	Str_SPrintf(path, sizeof(path), "%s\\%s\\%s%s.dll", savePath, game, name, CPU_STRING);

	return LoadLibrary(path);
}

/*
 ==================
 Sys_FreeLibrary
 ==================
*/
void Sys_FreeLibrary (void *libHandle){

	if (!libHandle)
		Com_Error(true, "Sys_FreeLibrary: NULL libHandle");

	if (!FreeLibrary((HMODULE)libHandle))
		Com_Error(true, "Sys_FreeLibrary: FreeLibrary() failed");
}

/*
 ==================
 Sys_GetProcAddress
 ==================
*/
void *Sys_GetProcAddress (void *libHandle, const char *procName){

	if (!libHandle)
		Com_Error(true, "Sys_GetProcAddress: NULL libHandle");

	return (void*)GetProcAddress((HMODULE)libHandle, procName);
}


// ============================================================================


/*
 ==================
 Sys_Print
 ==================
*/
void Sys_Print (const char *text){

	Sys_ConsolePrint(text);

	OutputDebugString(text);
}

/*
 ==================
 Sys_Error
 ==================
*/
void Sys_Error (const char *fmt, ...){

	char	message[MAX_PRINT_MESSAGE];
	va_list	argPtr;
	MSG		msg;

	// Make sure all subsystems are down
	Com_Shutdown();

	// Get the message
	va_start(argPtr, fmt);
	Str_VSPrintf(message, sizeof(message), fmt, argPtr);
	va_end(argPtr);

	// Echo to console
	Sys_Print("\n");
	Sys_Print(message);
	Sys_Print("\n");

	// Show the error message
	Sys_SetConsoleError(message);

	// Wait for the user to quit
	while (1){
		if (!GetMessage(&msg, NULL, 0, 0))
			Sys_Quit();

		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}

/*
 ==================
 Sys_GenerateEvents
 ==================
*/
int Sys_GenerateEvents (void){

	MSG		msg;

	// Pump the message loop
	while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)){
		if (!GetMessage(&msg, NULL, 0, 0))
			Sys_Quit();

		// Special case for editor windows
		if (sys.hWndEditor){
			if (IsDialogMessage(sys.hWndEditor, &msg))
				continue;
		}

		// Dispatch the message
		sys.msgTime = msg.time;

		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	// Return the current time
	return timeGetTime();
}

/*
 ==================
 Sys_GetClipboardText
 ==================
*/
const char *Sys_GetClipboardText (void){

	HANDLE	hClipboardData;
	void	*data;
	char	*text;
	int		size;

	if (!OpenClipboard(NULL))
		return NULL;

	hClipboardData = GetClipboardData(CF_TEXT);
	if (!hClipboardData){
		CloseClipboard();
		return NULL;
	}

	data = GlobalLock(hClipboardData);
	if (!data){
		CloseClipboard();
		return NULL;
	}

	size = GlobalSize(hClipboardData);
	text = (char *)Mem_Alloc(size + 1, TAG_TEMP);

	memcpy(text, data, size);
	text[size] = 0;

	GlobalUnlock(hClipboardData);
	CloseClipboard();

	return text;
}

/*
 ==================
 Sys_OpenURL
 ==================
*/
void Sys_OpenURL (const char *url, bool quit){

	int		code;

	code = (int)ShellExecute(NULL, "open", url, NULL, NULL, SW_SHOW);
	if (code <= 32){
		Com_Printf(S_COLOR_RED "Could not open URL: %s\n", url);
		return;
	}

	if (quit)
		Sys_Quit();
}

/*
 ==================
 Sys_Sleep
 ==================
*/
void Sys_Sleep (int msec){

	if (msec < 0)
		msec = 0;

	Sleep(msec);
}

/*
 ==================
 Sys_Milliseconds
 ==================
*/
int Sys_Milliseconds (void){

	return timeGetTime();
}

/*
 ==================
 Sys_GetClockTicks
 ==================
*/
double Sys_GetClockTicks (void){

	double	ticks;

	if (!QueryPerformanceCounter((LARGE_INTEGER *)&ticks))
		return 0.0;

	return ticks;
}

/*
 ==================
 Sys_ClockTicksPerSecond
 ==================
*/
double Sys_ClockTicksPerSecond (void){

	return sys.ticksPerSecond;
}

/*
 ==================
 Sys_GetProcessorId
 ==================
*/
int Sys_GetProcessorId (void){

	return sys.cpuId;
}

/*
 ==================
 Sys_GetProcessorString
 ==================
*/
const char *Sys_GetProcessorString (void){

	return sys.cpuString;
}

/*
 ==================
 Sys_DetectProcessor
 ==================
*/
static void Sys_DetectProcessor (void){

	char	vendor[16];
	dword	stdFlags[4], extFlags[4];

	// If the CPUID instruction is supported, get the vendor identifier and
	// both the standard and the extended CPUID flags
#ifdef _GCC_
	__asm {
		pushfd
		pop		eax
		test	eax, 0x00200000
		jz		setBit21
		and		eax, 0xFFDFFFFF
		push	eax
		popfd
		pushfd
		pop		eax
		test	eax, 0x00200000
		jz		getInfo
		jmp		noCPUID

setBit21:
		or		eax, 0x00200000
		push	eax
		popfd
		pushfd
		pop		eax
		test	eax, 0x00200000
		jnz		getInfo
		jmp		noCPUID

getInfo:
		mov		eax, 0x00000000
		cpuid
		mov		dword ptr[vendor +  0], ebx
		mov		dword ptr[vendor +  4], edx
		mov		dword ptr[vendor +  8], ecx
		mov		dword ptr[vendor + 12], 0

		mov		eax, 0x00000001
		cpuid
		mov		dword ptr[stdFlags +  0], eax
		mov		dword ptr[stdFlags +  4], ebx
		mov		dword ptr[stdFlags +  8], ecx
		mov		dword ptr[stdFlags + 12], edx

		mov		eax, 0x80000000
		cpuid
		cmp		eax, 0x80000000
		jbe		noExtFunctions

		mov		eax, 0x80000001
		cpuid
		mov		dword ptr[extFlags +  0], eax
		mov		dword ptr[extFlags +  4], ebx
		mov		dword ptr[extFlags +  8], ecx
		mov		dword ptr[extFlags + 12], edx
		jmp		hasCPUID

noExtFunctions:
		mov		dword ptr[extFlags +  0], 0
		mov		dword ptr[extFlags +  4], 0
		mov		dword ptr[extFlags +  8], 0
		mov		dword ptr[extFlags + 12], 0
		jmp		hasCPUID
	}

noCPUID:
	// Unsupported CPU (386/486, etc)
	sys.cpuId = CPUID_UNSUPPORTED;

	Str_Copy(sys.cpuString, "Unsupported CPU", sizeof(sys.cpuString));

	return;

hasCPUID:
	// Get the CPU name
	if (!Str_Compare(vendor, "AuthenticAMD")){
		sys.cpuId = CPUID_AMD;

		Str_Copy(sys.cpuString, "AMD CPU", sizeof(sys.cpuString));
	}
	else if (!Str_Compare(vendor, "GenuineIntel")){
		sys.cpuId = CPUID_INTEL;

		Str_Copy(sys.cpuString, "Intel CPU", sizeof(sys.cpuString));
	}
	else {
		sys.cpuId = CPUID_GENERIC;

		Str_Copy(sys.cpuString, "Generic CPU", sizeof(sys.cpuString));
	}

	// Get extended instruction sets supported
	if (stdFlags[3] & BIT(23))
		sys.cpuId |= CPUID_MMX;
	if (extFlags[3] & BIT(22))
		sys.cpuId |= CPUID_MMX_EXT;
	if (extFlags[3] & BIT(31))
		sys.cpuId |= CPUID_3DNOW;
	if (extFlags[3] & BIT(30))
		sys.cpuId |= CPUID_3DNOW_EXT;
	if (stdFlags[3] & BIT(25))
		sys.cpuId |= CPUID_SSE;
	if (stdFlags[3] & BIT(26))
		sys.cpuId |= CPUID_SSE2;
	if (stdFlags[2] & BIT(0))
		sys.cpuId |= CPUID_SSE3;
	if (stdFlags[2] & BIT(9))
		sys.cpuId |= CPUID_SSE4;
	if (stdFlags[3] & BIT(28))
		sys.cpuId |= CPUID_HTT;

	if (sys.cpuId & CPUID_MMX){
		Str_Append(sys.cpuString, " w/ MMX", sizeof(sys.cpuString));

		if (sys.cpuId & CPUID_MMX_EXT)
			Str_Append(sys.cpuString, "+", sizeof(sys.cpuString));

		if (sys.cpuId & CPUID_3DNOW){
			Str_Append(sys.cpuString, " & 3DNow!", sizeof(sys.cpuString));

			if (sys.cpuId & CPUID_3DNOW_EXT)
				Str_Append(sys.cpuString, "+", sizeof(sys.cpuString));
		}

		if (sys.cpuId & CPUID_SSE){
			Str_Append(sys.cpuString, " & SSE", sizeof(sys.cpuString));

			if (sys.cpuId & CPUID_SSE2)
				Str_Append(sys.cpuString, " & SSE2", sizeof(sys.cpuString));
			if (sys.cpuId & CPUID_SSE3)
				Str_Append(sys.cpuString, " & SSE3", sizeof(sys.cpuString));
			if (sys.cpuId & CPUID_SSE4)
				Str_Append(sys.cpuString, " & SSE4", sizeof(sys.cpuString));
		}

		if (sys.cpuId & CPUID_HTT)
			Str_Append(sys.cpuString, " & HTT", sizeof(sys.cpuString));
	}
#else
	Str_Copy(sys.cpuString, "Generic CPU", sizeof(sys.cpuString));
#endif
}

/*
 ==================
 Sys_Init
 ==================
*/
void Sys_Init (void){

	OSVERSIONINFOEX	osVersionInfo;
	MEMORYSTATUSEX	memoryStatus;
	char			string[256];
	ulong			len;

	Com_Printf("------- System Initialization -------\n");

	// Get OS version
	osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

	if (!GetVersionEx((OSVERSIONINFO *)&osVersionInfo))
		Com_Error(true, "Couldn't get OS version info");

	if (osVersionInfo.dwPlatformId == VER_PLATFORM_WIN32s)
		Com_Error(true, ENGINE_NAME " doesn't run on Win32s");
	if (osVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
		Com_Error(true, ENGINE_NAME " doesn't run on Windows 95/98/Me");

	if (osVersionInfo.dwPlatformId != VER_PLATFORM_WIN32_NT || osVersionInfo.dwMajorVersion < 5)
		Com_Error(true, ENGINE_NAME " requires Windows NT 5.0 or greater");

	if (osVersionInfo.dwMajorVersion == 5 && osVersionInfo.dwMinorVersion == 0){
		if (osVersionInfo.wProductType == VER_NT_WORKSTATION)
			Str_Copy(string, "Windows 2000", sizeof(string));
		else
			Str_Copy(string, "Windows 2000 Server", sizeof(string));
	}
	else if (osVersionInfo.dwMajorVersion == 5 && osVersionInfo.dwMinorVersion == 1)
		Str_Copy(string, "Windows XP", sizeof(string));
	else if (osVersionInfo.dwMajorVersion == 5 && osVersionInfo.dwMinorVersion == 2)
		Str_Copy(string, "Windows Server 2003", sizeof(string));
	else if (osVersionInfo.dwMajorVersion == 6 && osVersionInfo.dwMinorVersion == 0){
		if (osVersionInfo.wProductType == VER_NT_WORKSTATION)
			Str_Copy(string, "Windows Vista", sizeof(string));
		else
			Str_Copy(string, "Windows Server Longhorn", sizeof(string));
	}
	else
		Str_SPrintf(string, sizeof(string), "Windows %i.%i", osVersionInfo.dwMajorVersion, osVersionInfo.dwMinorVersion);

	Com_Printf("OS: %s\n", string);
	CVar_Get("sys_osVersion", string, CVAR_ROM, "OS version");

	// Detect CPU
	Sys_DetectProcessor();

	Com_Printf("%s\n", sys.cpuString);
	CVar_Get("sys_cpuString", sys.cpuString, CVAR_ROM, "CPU string");

	// Get system memory
	memoryStatus.dwLength = sizeof(MEMORYSTATUSEX);

	GlobalMemoryStatusEx(&memoryStatus);
	Str_SPrintf(string, sizeof(string), "%I64u MB", memoryStatus.ullTotalPhys >> 20);

	Com_Printf("%s system memory\n", string);
	CVar_Get("sys_memory", string, CVAR_ROM, "System memory");

	// Get user name
	len = sizeof(string);
	if (GetUserName(string, &len))
		CVar_Get("sys_userName", string, CVAR_ROM, "User name");
	else
		CVar_Get("sys_userName", "", CVAR_ROM, "User name");

	// Initialize system timers
	timeBeginPeriod(1);

	if (!QueryPerformanceFrequency((LARGE_INTEGER *)&sys.ticksPerSecond))
		Com_Error(true, "No hardware timer available");

	Com_Printf("-------------------------------------\n");
}

/*
 ==================
 Sys_Shutdown
 ==================
*/
void Sys_Shutdown (void){

	timeEndPeriod(1);
}

/*
 ==================
 Sys_Quit
 ==================
*/
void Sys_Quit (void){

	Com_Shutdown();

	Sys_DestroyConsole();

	ExitProcess(0);
}


// ============================================================================


/*
 ==================
 WinMain
 ==================
*/
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){
	// Previous instances do not exist in Win32
	if (hPrevInstance)
		return 0;

	sys.hInstance = 0; //hInstance;
	// No abort/retry/fail errors
	SetErrorMode(SEM_FAILCRITICALERRORS);

	// Create the dedicated console
	Sys_CreateConsole();

	// Initialize all the subsystems
	Com_Init(lpCmdLine);

	// Main program loop
	while (1){
		// If not running a dedicated server and not active, sleep a bit
		if (!com_dedicated->integerValue && !sys.active)
			Sleep(5);

		// Run a frame
		Com_Frame();
	}

	// Never gets here
	return 1;
}

int main(int argc, char **argv){
	char cmdline[MAX_PATH];
	for(int c=0;c<argc;c++){
		strcat(cmdline,argv[c]);
		strcat(cmdline," ");
	}
	return WinMain(GetModuleHandle(NULL),0,"Quantum",1);
}
